package com.cetc.sdp.kmga.cs.audit;

import com.cetc.sdp.kmga.cs.util.Tool;

import java.io.Serializable;
import java.time.DayOfWeek;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.*;

/**
 * @description: 审计日志规则匹配
 * @author： DengQiang
 * @date: 2017/8/23 9:24
 */
public class AuditRuleFastMatcher implements Serializable {
    /**
     * 规则
     */
    private List<AuditRule> auditRules;
    /**
     * 资源id，规则映射
     */
    private Map<Integer, List<AuditRule>> resourceBlacklist;
    /**
     * 对全部资源生效的规则
     */
    private List<AuditRule> emptyResBlacklist;
    /**
     * 操作码映射
     */
    private Map<String, OpCode> opMapping;

    private Map<String, OpCode> opTypeCodeMapping;
    /**
     * 风险等级映射
     */
    private Map<Integer, Integer> riskLevelMapping;
    /**
     * 资源id查找表
     */
    private Map<ResourceKey, ResourceAuditObj> resourceAuditObjLookup;

    public AuditRuleFastMatcher(List<AuditRule> auditRules, List<ResourceAuditObj> resourceAuditObjs, Map<String, OpCode> opMapping, Map<Integer, Integer> riskLevelMapping) {
        this.auditRules = auditRules;
        this.resourceBlacklist = new HashMap<>();
        this.emptyResBlacklist = new ArrayList<>();
        this.opTypeCodeMapping = new HashMap<>();

        //根据资源id，将规则分为两类
        auditRules.forEach(auditRule -> {
            Integer resId = auditRule.getResId();
            if (resId == null) {
                this.emptyResBlacklist.add(auditRule);
            } else {
                List<AuditRule> rules = this.resourceBlacklist.computeIfAbsent(resId, k -> new ArrayList<>());
                rules.add(auditRule);
            }
        });

        this.opMapping = opMapping;
        this.riskLevelMapping = riskLevelMapping;

        this.resourceAuditObjLookup = new HashMap<>();
        for (ResourceAuditObj obj : resourceAuditObjs) {
            this.resourceAuditObjLookup.put(new ResourceKey(obj.getServerIp(), obj.getPort(), obj.getSrc()), obj);
        }
        this.opMapping.forEach((key, opCode) -> this.opTypeCodeMapping.put(opCode.getOperTypeCode(), opCode));

    }

    /**
     * 告警规则匹配，匹配成功返回AuditRuleMatchResult，否则返回null
     * @param resId 资源id
     * @param ip 客户端ip
     * @param time 时间
     * @param opStr 操作
     * @return
     */
    public AuditRuleMatchResult match(Integer resId, String ip, LocalDateTime time, String opStr) {
        if (resId != null) {
            List<AuditRule> auditRules = this.resourceBlacklist.get(resId);
            if(auditRules != null) {
                for (AuditRule rule : auditRules) {
                    AuditRuleMatchResult res = matchRule(resId, ip, time, opStr, rule);
                    if (res != null) {
                        return res;
                    }
                }
            }
        }
        for (AuditRule auditRule : this.emptyResBlacklist) {
            matchRule(resId, ip, time, opStr, auditRule);
        }
        return null;
    }


    /**
     *
     * @param serverIp ip
     * @param port 端口
     * @param src 来源
     * @return
     */
    public ResourceAuditObj getResourceId(String serverIp, Integer port, Integer src) {
        if (serverIp == null || port == null || src == null) {
            return null;
        }
        return this.resourceAuditObjLookup.get(new ResourceKey(serverIp, port, src));
    }

    private AuditRuleMatchResult matchRule(Integer resId, String ip, LocalDateTime time, String opStr, AuditRule auditRule) {
        List<AuditRuleCondition> conditions = auditRule.getAuditRuleConditions();
        if (conditions != null) {
            for (AuditRuleCondition rc : conditions) {
                if (matchIp(ip, rc.getIpSet()) && matchTime(time, rc) && matchOp(opStr, rc)) {
                    AuditRuleMatchResult rmr = new AuditRuleMatchResult();
                    rmr.setRuleId(auditRule.getRuleId());
                    rmr.setRuleName(auditRule.getName());
                    rmr.setRuleConditionId(rc.getId());
                    rmr.setRiskLevel(auditRule.getRiskLevel());
                    return rmr;
                }
            }
        } else {
            return null;
        }

        return null;
    }

    public String getOpTypeCode(String opStr) {
        if (opStr == null || opStr.isEmpty()) {
            //unknown op
            return "99";
        }
        OpCode opCode = this.opMapping.get(opStr.toLowerCase());
        if (opCode != null) {
            return opCode.getOperTypeCode();
        } else {
            return "99";
        }
    }

    public OpCode getOpCode(String opTypeCode) {
        if (opTypeCode == null) {
            return null;
        }
        return this.opTypeCodeMapping.get(opTypeCode);
    }

    public int getRiskLevel(Integer origLevel) {
        return this.riskLevelMapping.getOrDefault(origLevel, 0);
    }


    private boolean matchIp(String ip, List<IPSet> ipSets) {
        if (ip == null) {
            return false;
        }
        if (ipSets == null || ipSets.isEmpty()) {
            return true;
        }
        for (IPSet ipSet : ipSets) {
            Set<String> set = ipSet.getIpSet();
            if (set != null && set.contains(ip)) {
                return true;
            }

            List<IPSet.IpSeg> ipSegs = ipSet.getIpSegs();
            Long ipLong = Tool.getStringIpToLong(ip);
            for (IPSet.IpSeg seg : ipSegs) {
                if (seg.in(ipLong)) {
                    return true;
                }
            }
        }
        return false;
    }

    private boolean matchTime(LocalDateTime dataTime, AuditRuleCondition condition) {
        if (dataTime == null) {
            return false;
        }
        DayOfWeek dayOfWeek = dataTime.getDayOfWeek();

        LocalTime time = dataTime.toLocalTime();
        LocalTime start = condition.getDayStart();
        LocalTime end = condition.getDayEnd();

        return  condition.getWeekDays().contains(dayOfWeek) &&
                start != null && end != null && time.compareTo(start) >= 0 && time.compareTo(end) <= 0;
    }

    private boolean matchOp(String opStr, AuditRuleCondition condition) {
        if (opStr == null) {
            return false;
        }
        String opTypeCode = getOpTypeCode(opStr);
        Set<String> opType = condition.getOperationType();
        return opType == null || opType.contains(opTypeCode) || opType.contains("all");
    }
}
